﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text;
using smp.QueryDb.Structure;

namespace smp.QueryDb
{
    internal class QueryStructure
    {
        private readonly List<Sort> _sorts = new List<Sort>();
        private readonly List<Map> _maps = new List<Map>();
        private readonly List<string> _rawColumns = new List<string>();
        private readonly List<string> _rawOutputColumns = new List<string>();
        private readonly List<Group> _groupbys = new List<Group>();
        private FilterStructure _filter;

        internal const string TotalRecordsFieldName = "TotalCount";
        internal Table Table { get; private set; }
        internal int PageSize { get; private set; }
        internal int Page { get; private set; }
        internal int? Top { get; private set; }

        internal string FromStatement { get { return Table.FromStatement; } }
        internal string ColumnStatement { get; private set; }
        internal string OrderStatement { get; private set; }
        internal string WhereStatement { get; private set; }
        internal string GroupStatement { get; private set; }
        internal IList<IParameter> Parameters { get; private set; }
        internal List<string> RawColumns { get { return _rawOutputColumns; } }
        internal bool HasPaging { get { return Page > 0 && PageSize > 0; } }
        internal List<Map> Maps { get { return _maps; } }

        internal void SetTop(int top)
        {
            Top = top;
        }

        internal void SetFromStatement(Table table)
        {
            Table = table;
        }

        internal void AddSort(Sort sort)
        {
            _sorts.Add(sort);
        }

        internal void AddGroupBy(Group group)
        {
            _groupbys.Add(group);
        }


        internal void SetPage(int page)
        {
            Page = page;
        }

        internal void SetPageSize(int pageSize)
        {
            PageSize = pageSize;
        }

        internal void AddMap(PropertyInfo property, Column column)
        {
            if (_maps.Find(x => x.Property.Name == property.Name) != null)
            {
                throw Helper.Error("Duplicate property mapping declaration, cannot map property {0} twice.", property.Name);
            }
            var map = new Map(property, column);
            _maps.Add(map);
        }

        internal void AddNestedMap(PropertyInfo property, List<Map> maps)
        {
            if (_maps.Find(x => x.Property.Name == property.Name) != null)
            {
                throw Helper.Error("Duplicate property mapping declaration, cannot map property {0} twice.", property.Name);
            }
            var nestedMap = new Map(property, maps);
            _maps.Add(nestedMap);
        }

        internal void SetFilter(FilterStructure filter)
        {
            if (_filter != null)
            {
                throw Helper.Error("Invalid duplicate Where call on Query<T>");
            }
            _filter = filter;
        }

        internal void Select(Column column)
        {
            if (!Table.IsTableDeclared(column.TableDeclaration))
            {
                throw Helper.Error("Invalid select column {0}, declared table {1} is not defined in the from statement",
                                   column.Name,
                                   column.TableDeclaration);
            }
            var declaration = column.Declaration;
            if (_rawColumns.Contains(declaration))
            {
                throw Helper.Error("Duplicate select statement, column {0} already defined for select", declaration);
            }
            _rawColumns.Add(declaration);
            _rawOutputColumns.Add(column.OutputDeclaration);
        }

        internal void Finalise(IDbAdapter adapter, bool isRaw)
        {
            var statementBuilder = new StringBuilder();
            foreach (var sort in _sorts)
            {
                sort.AppendStatementBuilder(_maps,  statementBuilder, Table);
            }
            if(statementBuilder.Length > 0 && statementBuilder.ToString().EndsWith(","))
            {
                statementBuilder.Remove(statementBuilder.Length - 1, 1);
            }
            OrderStatement = statementBuilder.ToString();


            statementBuilder.Clear();
            foreach(var groupBy in _groupbys)
            {
                if(PageSize > 0)
                {
                    throw new Exception("Group by not currently supported for paged queries.");
                }
                groupBy.AppendStatementBuilder(_maps, statementBuilder, Table);
            }
            if (statementBuilder.Length > 0 && statementBuilder.ToString().EndsWith(","))
            {
                statementBuilder.Remove(statementBuilder.Length - 1, 1);
            }
            GroupStatement = statementBuilder.ToString();


            statementBuilder.Clear();
            if (_filter != null)
            {
                _filter.ResetParameters();
                var parameterIndex = 0;
                _filter.AppendStatementBuilder(ref parameterIndex, Table, _maps,  statementBuilder, adapter);
                WhereStatement = statementBuilder.ToString();
                Parameters = _filter.Parameters;
            }


            statementBuilder.Clear();
            var columnIndex = 0;
            if (isRaw)
            {
                foreach (var column in _rawColumns)
                {
                    statementBuilder.AppendFormat(" {0},", column);
                }
            }
            else
            {
                foreach (var map in _maps)
                {
                    map.AppendStatementBuilder(ref columnIndex, Table,  statementBuilder);
                }
            }
            if(statementBuilder.Length > 0 && statementBuilder.ToString().EndsWith(","))
            {
                statementBuilder.Remove(statementBuilder.Length - 1, 1);
            }
            ColumnStatement = statementBuilder.ToString();
        }

        internal object CreateModel(Type modelType, IDataRecord record)
        {
            var model = Activator.CreateInstance(modelType);
            foreach (var map in _maps)
            {
                map.PopulateModel(record,  model);
            }
            return model;
        }

        internal IDbCommand GenerateCommand(IDbConnection connection, IDbAdapter adapter)
        {
            ICommand command;
            if (Page > 0 && PageSize > 0)
            {
                command = adapter.GeneratePagedQuery(ColumnStatement,
                                                     FromStatement,
                                                     WhereStatement,
                                                     OrderStatement,
                                                     Page,
                                                     PageSize,
                                                     Parameters);
            }
            else if(Top.HasValue && Top.Value > 0)
            {
                command = GenerateTopCommand();
            }
            else
            {
                command = GenerateSelectAllCommand();
            }
            return Helper.CreateAdoCommand(connection, command, CommandType.Text, null);
        }

        internal IDbCommand GenerateTopCommand(IDbConnection connection, IDbAdapter adapter, int topCount)
        {
            var command = new Command();
            command.CommandBuilder.AppendFormat("SELECT TOP {0} {1} FROM {2} ",
                                                topCount,
                                                ColumnStatement
                                                , FromStatement);
            if (!string.IsNullOrEmpty(WhereStatement))
            {
                command.CommandBuilder.AppendFormat("WHERE {0} "
                                                    , WhereStatement);
            }
            if (!string.IsNullOrEmpty(OrderStatement))
            {
                command.CommandBuilder.AppendFormat("ORDER BY {0} "
                                                    , OrderStatement);
            }
            if (Parameters != null)
            {
                command.AddParameters(Parameters);
            }
            return Helper.CreateAdoCommand(connection, command, CommandType.Text, null);
        }

        internal ICommand GenerateTopCommand()
        {
            var command = new Command();
            command.CommandBuilder.AppendFormat("SELECT TOP {0} {1} FROM {2} ",
                                                Top,
                                                ColumnStatement
                                                , FromStatement);
            if (!string.IsNullOrEmpty(WhereStatement))
            {
                command.CommandBuilder.AppendFormat("WHERE {0} "
                                                    , WhereStatement);
            }
            if(!string.IsNullOrEmpty(GroupStatement))
            {
                command.CommandBuilder.AppendFormat(GroupStatement);
            }
            if (!string.IsNullOrEmpty(OrderStatement))
            {
                command.CommandBuilder.AppendFormat("ORDER BY {0} "
                                                    , OrderStatement);
            }
            if (Parameters != null)
            {
                command.AddParameters(Parameters);
            }
            return command;
        }

        internal IDbCommand GenerateCountCommand(IDbConnection connection)
        {
            var command = new Command();
            command.CommandBuilder.AppendFormat("SELECT COUNT(*) AS {0} FROM {1}",
                                                TotalRecordsFieldName,
                                                FromStatement);
            if (!string.IsNullOrEmpty(WhereStatement))
            {
                command.CommandBuilder.AppendFormat(" WHERE {0} ", WhereStatement);
            }
            if (Parameters != null)
            {
                command.AddParameters(Parameters);
            }
            return Helper.CreateAdoCommand(connection, command, CommandType.Text, null);
        }

        internal ICommand GenerateSelectAllCommand()
        {
            var command = new Command();
            command.CommandBuilder.AppendFormat("SELECT {0} FROM {1} ",
                                                ColumnStatement
                                                , FromStatement);
            if (!string.IsNullOrEmpty(WhereStatement))
            {
                command.CommandBuilder.AppendFormat("WHERE {0} "
                                                    , WhereStatement);
            }
            if (!string.IsNullOrEmpty(GroupStatement))
            {
                command.CommandBuilder.AppendFormat("GROUP BY {0} ", GroupStatement);
            }
            if (!string.IsNullOrEmpty(OrderStatement))
            {
                command.CommandBuilder.AppendFormat("ORDER BY {0} "
                                                    , OrderStatement);
            }
            if (Parameters != null)
            {
                command.AddParameters(Parameters);
            }
            return command;
        }
    }
}
